package com.dianping.localcache;

import java.util.concurrent.locks.ReentrantLock;

import com.dennytech.tac.utils.DataCursor;
import com.dennytech.tac.utils.LinkedList;
import com.dennytech.tac.utils.Log;

public abstract class BaseCacheManager<E> extends AbstractCacheManager<E> {
	private static final String TAG = BaseCacheManager.class.getSimpleName();
	
	private static final double FULL_USEAGE = 1.0;
	private static final double OKAY_USEAGE = 0.85;

	private long lifetime;
	private int cacheCapacity;
	private boolean hitSensitive;
	private final ReentrantLock cleanLock = new ReentrantLock();

	public BaseCacheManager(CacheInfoManager infoManager, long lifetime,
			int cacheCapacity, boolean hitSensitive) {
		super(infoManager);
		this.lifetime = lifetime;
		this.cacheCapacity = cacheCapacity;
		this.hitSensitive = hitSensitive;
	}

	public long lifetime() {
		return lifetime;
	}

	/**
	 * the default implementation is to mix the create time to it's lifetime
	 */
	protected boolean isExpired(CacheInfo info) {
		long currentMillis = System.currentTimeMillis();
		return lifetime > 0
				&& (info.create + lifetime < currentMillis || info.create > currentMillis);
	}

	@Override
	protected CacheInfo hitCache(CacheInfo ci) {
		if (isExpired(ci)) {
			Log.i(TAG, "hitCache return null because expire");
			return null;
		}
		if (hitSensitive) {
			Log.i(TAG, "hitCache return new CahceInfo because hitSensitive = true");
			return new CacheInfo(ci.url, ci.size, System.currentTimeMillis());
		} else {
			return ci;
		}
	}

	@Override
	public void notifyChanged(int oldSize, int newSize, int oldCount,
			int newCount) {
		if (cacheCapacity <= 0)
			return;
		if (oldSize < newSize && newSize > (int) (cacheCapacity * FULL_USEAGE)) {
			cleanCache((int) (cacheCapacity * OKAY_USEAGE));
		}
	}

	public boolean cleanUp() {
		return cleanCache(0);
	}

	protected boolean cleanCache(int amount) {
		if (isClosed()) {
			Log.i(TAG, "cleanCache return false because closed");
			return false;
		}

		if (!cleanLock.tryLock()) {
			Log.i(TAG, "cleanCache return false because cleanLock.tryLock failed");
			return false;
		}
		try {
			int size = cacheInfoManager.getSize();
			// oldest in front
			LinkedList<CacheInfo> removes = new LinkedList<CacheInfo>();
			DataCursor<CacheInfo> c = cacheInfoManager.iteratorByCreate();
			try {
				while (c.moveToNext()) {
					CacheInfo ci = c.getData();
					if (isExpired(ci)) {
						Log.i(TAG, "cleanCache remove CacheInfo because it is expired");
						removes.addLast(ci);
						size -= ci.size;
					} else if (amount > 0 && size > amount) {
						Log.i(TAG, "cleanCache remove CacheInfo because it exceed size limit");
						removes.addLast(ci);
						size -= ci.size;
					} else {
						break;
					}
				}
			} finally {
				c.close();
			}
			for (CacheInfo ci : removes) {
				remove(ci.url);
			}
			return true;
		} finally {
			cleanLock.unlock();
		}
	}

	@Override
	public void close() {
		cleanLock.lock();
		try {
			super.close();
		} finally {
			cleanLock.unlock();
		}
	}
}
